package entityManager;

import dataStorage.DataHandler;
import dataStorage.DataXML;
import entity.CashFlow;
import entity.Category;
import entity.Expense;
import entity.Source;
import entity.CashFlow.Period;
import exception.*;
import settings.IdManager;
import settings.Settings;

import java.io.IOException;
import java.util.*;

import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;

import org.xml.sax.SAXException;

/**
 * @author A0098808L Yuen Shu Hui
 */
public class ExpenseManager implements Manager<Expense> {	

	private final DateComparator myComparator = new DateComparator();
	private ArrayList<Expense> allExpenseList;
	private final String fileKey = "expense";

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Default Constructor for ExpenseManager class 
	 * Also initialized the allExpenseList if the database exists.
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public ExpenseManager() throws TransformerConfigurationException,
	InstantiationException, IllegalAccessException, JAXBException,
	ParserConfigurationException, SAXException, IOException,
	TransformerException {
		loadAll();
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Initialize and replace local allExpenseList if external database(xml) exists. 
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public void loadAll() throws TransformerConfigurationException,
	InstantiationException, IllegalAccessException, JAXBException,
	ParserConfigurationException, SAXException, IOException,
	TransformerException {
		DataHandler dataHandler = DataXML.getInstance();
		allExpenseList = dataHandler.load(fileKey, Expense.class);
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Adding target Expense object to external xml file.
	 * @param add _target into external database(xml)
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public boolean add(Expense _target) throws TransformerConfigurationException, InstantiationException, IllegalAccessException, JAXBException, ParserConfigurationException, SAXException, IOException, TransformerException {
		if(_target == null)
		{
			System.out.println("Input object is NULL");
			return false;
		}
		else
		{
			/*
			 * IMPORTANT
			 * 1. Check if the id.xml has the id for this object present
			 * 2. NO: create a new entry with ID = 0 <handled by IdManager>
			 */
			if(!IdManager.getInstance().isPresent(fileKey)){
				IdManager.getInstance().addLastId(fileKey);
			}
			//get new id from the IdManger
			_target.setId(IdManager.getInstance().getLastId(fileKey));

			DataHandler dataHandler = DataXML.getInstance();
			if (dataHandler.write(_target, fileKey, Expense.class)) {
				/*
				 * IMPORTANT update the ID on the id.xml file
				 */
				if (IdManager.getInstance().updateLastId(fileKey)) {
					System.out.println("Expense Write Success");
					return true;
				} else
					System.out.println("Expense Write Fail");
			} else
				System.out.println("Expense Write Fail");
			return false;
		}
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Remove Expense object
	 * @param _target is the expense object to be deleted
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	public boolean remove(Expense _target) throws JAXBException,ParserConfigurationException, SAXException, IOException,TransformerException, InstantiationException, IllegalAccessException {
		if(_target == null || _target.getId() == null)
		{
			System.out.println("Input object is NULL");
			return false;
		}
		else{
			DataHandler dataHandler = DataXML.getInstance();
			if (dataHandler.remove(_target, fileKey)) {
				System.out.println("Remove Success");
				return true;
			} else
				System.out.println("Remove Fail");
			return false;
		}
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Update existing Expense with updated expense object
	 * @param _target is the target expense entry with the updated fields
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public boolean update(Expense _target) throws IOException, JAXBException,
	TransformerConfigurationException, ParserConfigurationException,
	SAXException, TransformerException, InstantiationException, IllegalAccessException {
		if(_target == null || _target.getId() == null)
		{
			System.out.println("Input object is NULL");
			return false;
		}
		else{
			DataHandler dataHandler = DataXML.getInstance();
			if (dataHandler.update(_target.getId(), _target, fileKey, Expense.class)) {
				System.out.println("Update Success");
				return true;
			} else {
				System.out.println("Update Fail");
			}
			return false;
		}
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Retrieve Expense entry from local allExpenseList by specify ID
	 * @param unique _id must be provided to uniquely identify the correct Expense entry
	 * @return expense object matches the ID, else return NULL
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public Expense getById(String _id)
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException {
		if(!_id.startsWith(fileKey))
		{
			System.out.println("Invalid ID");
		}
		else
		{
			loadAll();
			Iterator<Expense> lt = allExpenseList.iterator();
			Expense temp;

			while (lt.hasNext()) {
				temp = lt.next();
				if (temp.getId().equalsIgnoreCase(_id))
					return temp;
			}
		}
		return null;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Search and retrieve Expense object which matches _keyword in any part of the data fields in the Expense object stored in allExpenseList
	 * @param _keyword for searching
	 * @return list of Expense Object matches _keyword in any part of the data field OR an empty list;
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public ArrayList<Expense> getByKeyword(String _keyword)
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException {
		loadAll();
		ArrayList<Expense> matchList = new ArrayList<Expense>();

		Iterator<Expense> it = allExpenseList.iterator();
		Expense curr;
		while (it.hasNext()) {
			curr = it.next();
			if (curr.getName().toLowerCase().indexOf(_keyword.toLowerCase()) != -1) {
				matchList.add(curr);
			}
		}
		return matchList;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Retrieve all the Expense object stored in allExpenseList
	 * @return allExpenseList
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public ArrayList<Expense> getAll()
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException {
		loadAll();
		return allExpenseList;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Retrieve all the Expense object with a specific category
	 * @param _category 
	 * @return matchList contains all the expense object matches _category. An empty list is return if no expense object matches _category
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 * @throws NullVariableFound 
	 */
	public ArrayList<Expense> getByCategory(Category _category)
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException {
		loadAll();
		ArrayList<Expense> matchList = new ArrayList<Expense>();

		Iterator<Expense> it = allExpenseList.iterator();
		Expense curr;
		while (it.hasNext()) {
			curr = it.next();
			System.out.println("::: - "+curr.getCategory().getId() + " :::: "+_category.getId() + "  - "+_category.getName());
			if (curr.getCategory().getId().equals(_category.getId()))
				matchList.add(curr);
		}

		System.out.println(matchList.size());
		return matchList;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Retrieve expense object which carry amount inclusively within a range
	 * @param _min lower limit of the range
	 * @param _max upper limit of the range
	 * @return list containing expense object with amount within the range. Empty list is returned if no expense falls within the range 
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 * @throws NullVariableFound 
	 */
	public ArrayList<Expense> getByAmount(double _min, double _max)
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException, NullVariableFound {
		loadAll();
		ArrayList<Expense> matchList = new ArrayList<Expense>();

		Iterator<Expense> it = allExpenseList.iterator();
		Expense curr;
		while (it.hasNext()) {
			curr = it.next();
			if (curr.getAmount() >= _min && curr.getAmount() <= _max)
				matchList.add(curr);
		}

		return matchList;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Retrieve expense object which matches _source
	 * @param _source must be exist in SourceManager
	 * @return a list of expense matches _source, an empty list will be returned if no expense match _source
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 * @throws NullVariableFound 
	 */
	public ArrayList<Expense> getBySource(Source _source)
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException{
		loadAll();
		ArrayList<Expense> matchList = new ArrayList<Expense>();

		Iterator<Expense> it = allExpenseList.iterator();
		Expense curr;
		while (it.hasNext()) {
			curr = it.next();
			if (curr.getSource().getId().equals(_source.getId()))
				matchList.add(curr);
		}

		return matchList;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Retrieve expense object which carry date inclusively within the range
	 * @param _fromDate
	 * @param _toDate
	 * @return a list of expense fall within the date range, an empty list will be returned if no expense matches the parameter
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 * @throws NullVariableFound
	 */
	public ArrayList<Expense> getByDate(Calendar _fromDate, Calendar _toDate)
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException, NullVariableFound {
		loadAll();
		ArrayList<Expense> matchList = new ArrayList<Expense>();

		Iterator<Expense> it = allExpenseList.iterator();
		Expense curr;

		while (it.hasNext()) {
			curr = it.next();

			if (curr.getDate().after(_fromDate)
					&& curr.getDate().before(_toDate))
				matchList.add(curr);
			else {
				Calendar.getInstance();
				if (curr.getDate().get(Calendar.YEAR) == _fromDate.get(Calendar.YEAR)
						&& curr.getDate().get(Calendar.MONTH) == _fromDate.get(Calendar.MONTH)
						&& curr.getDate().get(Calendar.DATE) == _fromDate.get(Calendar.DATE))
					matchList.add(curr);
				else if (curr.getDate().get(Calendar.YEAR) == _toDate.get(Calendar.YEAR)
						&& curr.getDate().get(Calendar.MONTH) == _toDate.get(Calendar.MONTH)
						&& curr.getDate().get(Calendar.DATE) == _toDate.get(Calendar.DATE))
					matchList.add(curr);
			}
		}

		return matchList;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Get Sum of the total amount in allExpenseList
	 * @return sum
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public double getTotalAmount() throws TransformerConfigurationException,
	InstantiationException, IllegalAccessException, JAXBException,
	ParserConfigurationException, SAXException, IOException,
	TransformerException {
		loadAll();
		double sum = 0;

		Iterator<Expense> it = allExpenseList.iterator();

		while (it.hasNext()) {
			sum += it.next().getAmount();
		}

		return sum;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * get sum of the total expenses from the input list
	 * @param _listToBeCompute
	 * @return
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public double getTotalAmount(ArrayList<Expense> _listToBeCompute) throws TransformerConfigurationException,
	InstantiationException, IllegalAccessException, JAXBException,
	ParserConfigurationException, SAXException, IOException,
	TransformerException {
		double sum = 0;

		Iterator<Expense> it = _listToBeCompute.iterator();

		while (it.hasNext()) {
			sum += it.next().getAmount();
		}
		return sum;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * retrieve all the recursive expense object 
	 * @return list of recursive expense object
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 * @throws NullVariableFound
	 */
	public ArrayList<Expense> getRecursExpense()
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException{
		loadAll();
		ArrayList<Expense> matchList = new ArrayList<Expense>();
		Iterator<Expense> it = allExpenseList.iterator();
		Expense curr;

		while (it.hasNext()) {
			curr = it.next();
			if (curr.getRecurs())
				matchList.add(curr);
		}

		return matchList;
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Add an expense entry into the external file(xml) if the recursive expense item should be repeated in current run
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 * @throws NullVariableFound
	 * @throws IllegalInput 
	 */
	public void HandleRecursList(Calendar _currDate) throws TransformerConfigurationException,
	InstantiationException, IllegalAccessException, JAXBException,
	ParserConfigurationException, SAXException, IOException,
	TransformerException, NullVariableFound, IllegalInput {
		ArrayList<Expense> recursList = this.getRecursExpense();
		Iterator<Expense> it = recursList.iterator();
		Expense curr, temp;

		while (it.hasNext()) {
			curr = it.next();
			Calendar.getInstance();

			switch (curr.getRecursPeriod()) {
			case DAILY:
				temp = new Expense(curr.getName(), curr.getCategory(),
						curr.getAmount(), curr.getSource(), _currDate, false, null, curr.getId());
				this.add(temp);
				break;
			case WEEKLY:
				if (curr.getDate().get(Calendar.DAY_OF_WEEK) == _currDate
				.get(Calendar.DAY_OF_WEEK)) {
					temp = new Expense(curr.getName(), curr.getCategory(),
							curr.getAmount(), curr.getSource(), _currDate, false,
							null, curr.getId());
					this.add(temp);
				}
				break;
			case MONTHLY:
				if (curr.getDate().before(_currDate)
						&& curr.getDate().get(Calendar.DATE) == _currDate
						.get(Calendar.DATE)) {
					temp = new Expense(curr.getName(), curr.getCategory(),
							curr.getAmount(), curr.getSource(), _currDate, false,
							null, curr.getId());
					this.add(temp);
				}
				break;
			case YEARLY:
				if (curr.getDate().before(_currDate)
						&& curr.getDate().get(Calendar.DATE) == _currDate
						.get(Calendar.DATE)
						&& curr.getDate().get(Calendar.MONTH) == _currDate
						.get(Calendar.MONTH)) {
					temp = new Expense(curr.getName(), curr.getCategory(),
							curr.getAmount(), curr.getSource(), _currDate, false,
							null, curr.getId());
					this.add(temp);
				}
				break;
			default:
				throw new NullVariableFound("Recurs Period Unspecified");
			}
		}
	}


	/**
	 * @author A0098808L Yuen Shu Hui
	 * This acting as a Calculator utility to calculate the total amount by providing an arrayList
	 * @param _list
	 * @return sum
	 * @throws TransformerConfigurationException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws JAXBException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws TransformerException
	 */
	public double getTotalAmountByList(ArrayList<Expense> _list)
			throws TransformerConfigurationException, InstantiationException,
			IllegalAccessException, JAXBException,
			ParserConfigurationException, SAXException, IOException,
			TransformerException {
		double sum = 0;

		Iterator<Expense> it = _list.iterator();

		while (it.hasNext()) {
			sum += it.next().getAmount();
		}
		return sum;
	}

	// sorting
	/**
	 * @author A0098808L Yuen Shu Hui
	 * Sort input arrayList in ascending order
	 */
	public void SortListAscendingDate(ArrayList<Expense> _list)
	{
		Collections.sort(_list, myComparator);
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Sort input arrayList in descending order 
	 */
	public void SortListDescendingDate(ArrayList<Expense> _list)
	{
		Collections.sort(_list,Collections.reverseOrder(myComparator));
	}

	/**
	 * @author A0098808L Yuen Shu Hui
	 * Customize comparator for sorting the arraylist using date field
	 */
	class DateComparator implements Comparator<CashFlow>{
		public int compare(CashFlow obj1, CashFlow obj2){   
			Date a = (obj1).getDate().getTime();    
			Date b = (obj2).getDate().getTime();  

			if(a.before(b))
				return -1;
			else if(a.after(b))
				return 1;
			else
				return 0;
		}
	}


}
